Skip to content

fix(trustify): merge remediation data across purl_statuses with advisory attribution (TC-5019)#640

Merged
ruromero merged 10 commits into
guacsec:mainfrom
ruromero:TC-5019
Jul 8, 2026
Merged

fix(trustify): merge remediation data across purl_statuses with advisory attribution (TC-5019)#640
ruromero merged 10 commits into
guacsec:mainfrom
ruromero:TC-5019

Conversation

@ruromero

@ruromero ruromero commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes TC-5019 — part of the Trustify v3 migration (TC-4509).

  • Fix first-wins dedup bug: TrustifyResponseHandler.toIssues() used a HashMap keyed by source:cveId with a containsKey guard that discarded subsequent purl_statuses for the same CVE, losing their remediations. Now calls mergeIssueData() to accumulate fixedIn versions (deduped), add RemediationInfo entries, and keep the highest CVSS score.
  • Advisory attribution: Extracts document_id, title, and identifier URL from each affected entry to populate AdvisoryInfo on RemediationInfo. Sets RemediationCategory.VENDOR_FIX when fixed versions exist.
  • Model bump: trustify-da-api-model 2.0.9 → 2.0.11-SNAPSHOT (adds AdvisoryInfo, updated RemediationInfo with advisory field, RemediationCategory)

Test plan

  • testResponseToIssuesMergesRemediationsAcrossAffected — Two affected entries for same CVE merge into one Issue with combined fixedIn and two RemediationInfo items
  • testResponseToIssuesWithAdvisoryInfoAttribution — AdvisoryInfo populated correctly (id, title, url for URL identifiers, null url for non-URL identifiers)
  • testResponseToIssuesMergeKeepsHigherCvss — Merge retains higher CVSS score, does not overwrite with lower value
  • All 23 TrustifyResponseHandlerTest tests pass
  • 4 pre-existing AnalysisTest failures confirmed on main (unrelated)

Jira: TC-5019

Summary by Sourcery

Merge remediation and advisory data across multiple affected entries for the same issue, ensuring correct CVSS selection and remediation aggregation.

New Features:

  • Attach advisory metadata (id, title, URL) to remediations and classify them with remediation categories.

Bug Fixes:

  • Prevent loss of remediation data by merging multiple purl_status/affected entries for the same CVE into a single issue and retaining the highest CVSS score.

Enhancements:

  • Deduplicate fixed version entries when aggregating remediation data across affected records.

Build:

  • Bump trustify-da API dependency to version 2.0.11-SNAPSHOT.

Tests:

  • Add TrustifyResponseHandler tests covering remediation merging, advisory attribution, and CVSS score selection.

@sourcery-ai

sourcery-ai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR fixes Trustify issue merging logic so multiple affected/advisory entries for the same CVE+source are consolidated into a single Issue, with deduped fixed versions, multiple remediation entries with advisory attribution, and correct CVSS selection, using new API model types from trustify-da-api 2.0.11-SNAPSHOT.

Sequence diagram for merging multiple affected entries into a single Issue

sequenceDiagram
  participant TrustifyResponseHandler
  participant Issue
  participant Remediation
  participant RemediationInfo

  TrustifyResponseHandler->>TrustifyResponseHandler: toIssues(response)
  TrustifyResponseHandler->>TrustifyResponseHandler: mergeIssueData(existingIssue, data)
  TrustifyResponseHandler->>TrustifyResponseHandler: getAdvisoryScore(existingIssue.id, scores)
  TrustifyResponseHandler->>Issue: cvssScore(score.score())
  TrustifyResponseHandler->>Issue: setSeverity(severity)
  TrustifyResponseHandler->>TrustifyResponseHandler: ensureRemediation(existingIssue)
  TrustifyResponseHandler->>Remediation: addFixedVersions(remediation, ranges)
  TrustifyResponseHandler->>TrustifyResponseHandler: buildRemediationInfo(data, hasFixedVersions)
  TrustifyResponseHandler->>Remediation: addRemediationsItem(remediationInfo)
Loading

File-Level Changes

Change Details Files
Merge multiple purl_status/affected entries for the same CVE+source into a single Issue while preserving all remediation data and choosing the highest CVSS score.
  • Replace early-return on duplicate CVE+source with mergeIssueData that updates an existing Issue instead of discarding subsequent entries.
  • In mergeIssueData, re-evaluate advisory scores from the new data and update the Issue’s cvssScore and severity only when the new score is higher than the existing one.
  • Ensure an Issue always has a Remediation object via ensureRemediation before adding fixed versions or remediation info.
src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/TrustifyResponseHandler.java
Accumulate and deduplicate fixed version information and attach advisory metadata to remediation entries, including remediation category when fixes exist.
  • Refactor fixed version extraction from status.ranges into addFixedVersions, which appends only new fixed versions to Issue.remediation.fixedIn.
  • Introduce buildRemediationInfo to construct RemediationInfo from affected entries using document_id, title, and identifier, mapping URL identifiers to AdvisoryInfo.url.
  • Set RemediationCategory.VENDOR_FIX on RemediationInfo when either the current affected entry or merged data yields at least one fixed version, and append each RemediationInfo to Issue.remediation.remediations.
src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/TrustifyResponseHandler.java
Add tests that cover remediation merging across affected entries, advisory attribution behavior, and CVSS merge semantics.
  • Add testResponseToIssuesMergesRemediationsAcrossAffected to verify that two affected entries for the same CVE+source produce one Issue with combined fixedIn versions and two RemediationInfo entries and keep the highest CVSS score.
  • Add testResponseToIssuesWithAdvisoryInfoAttribution to assert AdvisoryInfo fields (id, title, url) and RemediationCategory for URL vs non-URL identifiers and merged fixed versions.
  • Add testResponseToIssuesMergeKeepsHigherCvss to ensure that merging multiple scores for the same Issue retains the highest cvssScore and its severity.
src/test/java/io/github/guacsec/trustifyda/integration/providers/trustify/TrustifyResponseHandlerTest.java
Update Trustify DA API dependency to pick up new remediation/advisory model types.
  • Bump trustify-da-api.version from 2.0.9 to 2.0.11-SNAPSHOT in pom.xml to enable use of AdvisoryInfo, RemediationInfo, and RemediationCategory.
pom.xml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The CVSS handling in mergeIssueData duplicates logic from setCvssData; consider extracting a shared helper for choosing and applying the highest advisory score to reduce duplication and keep behavior aligned.
  • In buildRemediationInfo, the URL detection using identifier.startsWith("http") is quite loose; if possible, tighten this to a more robust URI validation or explicit scheme check to avoid misclassifying non-URL identifiers.
  • The RemediationCategory assignment in buildRemediationInfo is driven solely by hasFixedVersions; if other remediation types are expected (e.g., configuration changes or workarounds), consider extending this logic to distinguish categories based on advisory content rather than just fixed versions.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The CVSS handling in `mergeIssueData` duplicates logic from `setCvssData`; consider extracting a shared helper for choosing and applying the highest advisory score to reduce duplication and keep behavior aligned.
- In `buildRemediationInfo`, the URL detection using `identifier.startsWith("http")` is quite loose; if possible, tighten this to a more robust URI validation or explicit scheme check to avoid misclassifying non-URL identifiers.
- The RemediationCategory assignment in `buildRemediationInfo` is driven solely by `hasFixedVersions`; if other remediation types are expected (e.g., configuration changes or workarounds), consider extending this logic to distinguish categories based on advisory content rather than just fixed versions.

## Individual Comments

### Comment 1
<location path="src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/TrustifyResponseHandler.java" line_range="269-271" />
<code_context>
+    if (title != null) {
+      advisory.title(title);
+    }
+    var identifier = JsonUtils.getTextValue(node, "identifier");
+    if (identifier != null && identifier.startsWith("http")) {
+      advisory.url(URI.create(identifier));
+    }
+    var info = new RemediationInfo().advisory(advisory);
</code_context>
<issue_to_address>
**issue (bug_risk):** Creating a URI from unvalidated external data can throw runtime exceptions.

In `buildRemediationInfo`, `URI.create(identifier)` is called solely after a `startsWith("http")` check. If `identifier` is malformed, `URI.create` will throw `IllegalArgumentException` and can disrupt response handling. Please add more robust handling (e.g., catch `IllegalArgumentException` and skip setting the URL, or use a safer parsing/validation helper) so bad advisory data doesn’t break processing.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

ruromero and others added 2 commits July 7, 2026 11:00
…ory attribution (TC-5019)

Fix first-wins deduplication bug in TrustifyResponseHandler.toIssues() that
dropped remediation data when multiple purl_statuses existed for the same CVE.
When the same source:cveId key is encountered again, mergeIssueData() now
accumulates fixedIn versions (deduped), adds RemediationInfo entries, and
retains the highest CVSS score.

Populate the new AdvisoryInfo field on each RemediationInfo using advisory data
from Trustify v3 (document_id, title, identifier URL). Set RemediationCategory
to VENDOR_FIX when fixed versions exist.

Bump trustify-da-api-model to 2.0.11-SNAPSHOT which includes AdvisoryInfo,
updated RemediationInfo with advisory field, and RemediationCategory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…el 2.0.11

RemediationInfo.url() and AdvisoryInfo.url() now accept URI instead of
String. Wrap URI.create() calls in try-catch for malformed URLs and guard
non-URL advisory identifiers (e.g., GHSA IDs) from being stored as URLs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add advisory RemediationInfo objects to all test fixture issues to match
the new buildRemediationInfo fallback behavior in TrustifyResponseHandler.
Update remediations summary counts accordingly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@codecov-commenter

codecov-commenter commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 73.91304% with 30 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.84%. Comparing base (758ae5e) to head (3ccbe07).

Files with missing lines Patch % Lines
...on/providers/trustify/TrustifyResponseHandler.java 73.91% 13 Missing and 17 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##               main     #640      +/-   ##
============================================
+ Coverage     56.67%   56.84%   +0.16%     
- Complexity      836      859      +23     
============================================
  Files            92       92              
  Lines          4863     4940      +77     
  Branches        655      676      +21     
============================================
+ Hits           2756     2808      +52     
- Misses         1814     1828      +14     
- Partials        293      304      +11     
Flag Coverage Δ
integration-tests 56.84% <73.91%> (+0.16%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...acsec/trustifyda/model/trustify/Vulnerability.java 60.29% <ø> (ø)
...on/providers/trustify/TrustifyResponseHandler.java 79.54% <73.91%> (-6.47%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ruromero ruromero requested a review from a-oren July 7, 2026 17:02
ruromero and others added 4 commits July 7, 2026 23:41
Add vendor_fix and workaround remediation entries to WireMock mock
data so the non-empty remediations processing paths in
TrustifyResponseHandler (setCvssData lines 237-266, mergeIssueData
lines 351-381) are exercised by integration tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…eData

Extract three duplicated blocks into private helpers:
- extractScoreData: CVSS score/severity extraction from JSON
- processVersionRange: version range parsing with dedup flag
- processRemediations: remediation array processing with fallback

Also add method length convention to CONVENTIONS.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…field

The pypi recommendations endpoint deserializes into Vulnerability which
only has status/id/justification fields, not remediations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Trustify server now returns a remediations field on the
recommendations Vulnerability object. Add @JsonIgnoreProperties to
tolerate new fields without breaking deserialization.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@a-oren a-oren left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

ruromero and others added 3 commits July 8, 2026 11:24
When multiple purl_statuses for the same CVE+source are merged, remediations
were blindly appended without checking for duplicates. This produced repeated
entries in the output (e.g., the same NO_FIX_PLANNED advisory appearing
multiple times). Add isDuplicateRemediation guard using a composite key of
(category, details, advisory.id) before adding any remediation item.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…_statuses

Reproduces the exact scenario where two purl_statuses within the same
vulnerability have identical remediations (WORKAROUND + NO_FIX_PLANNED)
with the same advisory document_id, verifying only 2 unique RemediationInfo
items appear in the output instead of 4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…and dedup

The report.json and batch_report.json fixtures now reflect the actual
handler output after advisory attribution and remediation deduplication
were added.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ruromero ruromero merged commit 88bc0c6 into guacsec:main Jul 8, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants